#include <stdio.h>
#include <stdlib.h>
#include "vm.h"

//instantiate VM and init it with functions' names, init registers with zeros and execution finger with zero
static struct VM my_vm={{conditionalMove, arrayIndex, arrayAmendment, addition, multiplication, 
				division, not_and, halt, allocation, abandonment, output, input, loadProgram, orthography}, 
				{0,0,0,0,0,0,0,0}, 0, 0, 0};


void extract_indexes(dataType data, char indexes[3])
{
	indexes[0] = (data >> 6)&7; 	// register A
	indexes[1] = (data >> 3)&7; 	// register B
	indexes[2] = (data)&7;		// register C
}

//implementation of VM operators
/*
 * The register A receives the value in register B, unless the register C contains 0.
*/
void conditionalMove(dataType data)
{
	char indexes[3];
	extract_indexes(data, indexes);
	
	if (my_vm.registers[indexes[2]] == 0)
	{
		my_vm.registers[indexes[0]] = my_vm.registers[indexes[1]];
	}
}

/*
 * The register A receives the value stored at offset in register C in the array
 * identified by B.
*/
void arrayIndex(dataType data)
{
    	char indexes[3];
	extract_indexes(data, indexes);

        my_vm.registers[indexes[0]] = *(my_vm.heap[indexes[1]] + my_vm.registers[indexes[2]]);
}

/*
 * The array identified by A is amended at the offset in register B to store the
 * value in register C.
*/
void arrayAmendment(dataType data)
{
        char indexes[3];
	extract_indexes(data, indexes);

        *(my_vm.registers[indexes[0]] + my_vm.registers[indexes[1]]) = my_vm.registers[indexes[2]];
}

/*
 * The register A receives the value in register B plus the value in register C,
 * modulo 2^32.
*/
void addition(dataType data) 
{
	char indexes[3];
	extract_indexes(data, indexes);
	
	my_vm.registers[indexes[0]] = my_vm.registers[indexes[1]] + my_vm.registers[indexes[2]];
}

/*
 * The register A receives the value in register B times the value in register C,
 * modulo 2^32.
*/
void multiplication(dataType data) 
{
	char indexes[3];
	extract_indexes(data, indexes);
	
	my_vm.registers[indexes[0]] = my_vm.registers[indexes[1]] * my_vm.registers[indexes[2]];
}

/*
 * The register A receives the value in register B divided by the value in
 * register C, if any, where each quantity is treated treated as an unsigned 32
 * bit number.
*/
void division(dataType data) 
{
	char indexes[3];
	extract_indexes(data, indexes);
	
	if (my_vm.registers[indexes[2]] == 0) fail();
	my_vm.registers[indexes[0]] = my_vm.registers[indexes[1]] / my_vm.registers[indexes[2]];

}

/*
 * Each bit in the register A receives the 1 bit if either register B or register
 * C has a 0 bit in that position. Otherwise the bit in register A receives the 0 bit.
*/
void not_and(dataType data) 
{
	char indexes[3];
	extract_indexes(data, indexes);
	
	my_vm.registers[indexes[0]] = ~(my_vm.registers[indexes[1]] | my_vm.registers[indexes[2]]); //error!
	//[dz] why error?
}

/*
The universal machine stops computation.
*/
void halt() 
{
	exit(0);
}

/*
 * A new array is created with a capacity of platters commensurate to the value in
 * the register C. This new array is initialized entirely with platters holding
 * the value 0. A bit pattern not consisting of exclusively the 0 bit, and that
 * identifies no other active allocated array, is placed in the B register.
*/
void allocation(dataType data) 
{
	//[vc] and what ?! :-D
	//[dz] laik zet:
	char *calloc();
	char indexes[3];
	extract_indexes(data, indexes);
	
	my_vm.heapIndex++;
	my_vm.heap[my_vm.heapIndex] = (platter *) calloc(my_vm.registers[indexes[2]], sizeof(platter));
	my_vm.registers[indexes[1]] = my_vm.heapIndex;
}

/*
 * The array identified by the register C is abandoned. Future allocations may
 * then reuse that identifier.
*/
void abandonment(dataType data) 
{
	//[vc] and what ?! :-D
}

/*
 * The value in the register C is displayed on the console immediately. Only
 * values between and including 0 and 255 are allowed.
*/
void output(dataType data) 
{
	char indexes[3];
	extract_indexes(data, indexes);
	//TODO add check: value in register should be between 0 and 255 
	putchar(my_vm.registers[indexes[2]]);
}

/*
 * The universal machine waits for input on the console. When input arrives, the
 * register C is loaded with the input, which must be between and including 0
 * and 255. If the end of input has been signaled, then the register C is endowed
 * with a uniform value pattern where every place is pregnant with the 1 bit.
*/
void input(dataType data) {}

/*
 * The array identified by the B register is duplicated and the duplicate shall
 * replace the '0' array, regardless of size. The execution finger is placed to
 * indicate the platter of this array that is described by the offset given in C,
 * where the value 0 denotes the first platter, 1 the second, et cetera.
 * The '0' array shall be the most sublime choice for loading, and shall be
 * handled with the utmost velocity.
*/
void loadProgram(dataType data)
{
    	char indexes[3];
	extract_indexes(data, indexes);

        

}

/*
 * The value indicated is loaded into the register A forthwith.
*/
void orthography(dataType data)
{
    char AIndex;
    platter value;

    AIndex = (data >> 25)&7;
    value = data & 0x1FFFFFF;

    my_vm.registers[AIndex] = value;

}

void fail()
{
	printf("FAIL\n");
	exit(EXIT_FAILURE);
}

void execute(FILE* p_file)
{
	platter command, data;
	char opIndex;
	
	while (fgets(&command, 5, p_file) != NULL)
	{
		opIndex = command>>28;
		opIndex = opIndex & 7; //error
                //[dz] why error? just additional check in case shifted command contains not zeros

/*
		printf("command is %x\n\toperator index is %d\n", command, opIndex);
*/
		data = command & 0x1FFFFFFF; 
		my_vm.operators[opIndex](data);
	}
}
/*
	Compile me with 
		gcc vm.c -o vm
*/
int main(int argc, char **argv )
{
	if (argc <=1)
	{
		printf("usage: %s <file>\n", argv[0]);
		exit(EXIT_SUCCESS);
	}

	FILE* p_file = fopen(argv[1], "r");
	execute (p_file);
	fclose(p_file);
	
	return EXIT_SUCCESS;
}

	
